package org.erikaredmark.util.collection;
import java.util.Iterator;
/**
*
* Fixed size array that can always store additional items, at the cost of, if full, removing the
* 'oldest' item from the array.
* <p/>
* As old items are removed as needed, this collection only supports adding items and
* iteration.
* <p/>
* This class is not thread safe
*
* @author Erika Redmark
*
*/
public class RingArray<T> implements Iterable<T> {
// Current index in array.
private int index;
// actual backing array
private final T[] array;
/**
*
* Creates a new ring array of the given size.
*
* @param size
* size of ring. Must be positive and non-zero
*
*/
// We need to use unchecked casts to use backing array
@SuppressWarnings("unchecked")
public RingArray(int size) {
if (size <= 0) throw new IllegalArgumentException("Size must be positive and non-zero, got: " + size);
array = (T[]) new Object[size];
index = 0;
}
/**
*
* Returns the element at the front, or the 'earliest', of the ring. If the ring is
* empty this returns {@code null}.
*
* @return
* earliest element or {@code null} if ring is empty
*
*/
public T front() {
// index points to INSERTION point
int oneBefore = index - 1;
return oneBefore >= 0
? array[oneBefore]
: array[array.length - 1];
}
/**
*
* Returns the element at the back, or the 'oldest', of the ring that
* hasn't been deleted yet.
* <p/>
* If the ring is empty this returns {@code null}
*
* @return
* latest undeleted element or {@code null} if ring is empty
*
*/
public T back() {
// insertion point COULD be back if the value is non-null. That's
// the best case, and the always case once the ring fills up as it
// should, so we test it first. If not, since rings ALWAYS start filling
// at 0, null future entries mean 0 is the oldest.
final T item = array[index];
if (item != null) return item;
else return array[0];
}
/**
*
* Pushes the given object to the front of the array, making it the new 'earliest'
* object. If the ring is full, the latest object is bumped out.
*
* @param item
* item to place in ring
*
*/
public void pushFront(T item) {
array[index] = item;
incrementIndex();
}
// Increments the index, but if it passes array boundaries, moves it
// back to position 0
private void incrementIndex() {
++index;
if (index >= array.length) {
index = 0;
}
}
/**
*
* Returns an iterator over this ring array. Iteration starts at the most recent (the first)
* in the ring array and keeps going to the last. It is important that the array is not
* modified during iteration, as that will cause undefined behaviour.
* <p/>
* This iterator does not support removal
*
*/
@Override public Iterator<T> iterator() {
int earliestElement = index - 1;
if (earliestElement < 0) earliestElement = array.length - 1;
return new RingIterator(earliestElement);
}
private final class RingIterator implements Iterator<T> {
// This DECREMENTS, as going backwards is going from earliest
// to latest.
private int curIndex;
// We start right here. When we get back here iteration is over.
private final int start;
// Toggled to true when we roll back around
private boolean done;
// Start must point to the EARLIEST element, not the insertion point.
private RingIterator(final int start) {
curIndex = this.start = start;
// Precheck if this ring buffer is empty. If so, iterator isn't doing anything
if (array[curIndex] == null) done = true;
}
@Override public boolean hasNext() {
return !(done);
}
@Override public T next() {
int i = curIndex;
// Decrement curIndex before returning array. Toggle done if,
// after decrementing, it is back at start. Must do after decrementing
// or we will stop at start...
curIndex = curIndex != 0
? curIndex - 1
: array.length - 1;
if (curIndex == start) done = true;
// Precheck what is at next element. if it is null, this ring is not full and
// iteration must stop now
if (array[curIndex] == null) done = true;
return array[i];
}
@Override public void remove() {
throw new UnsupportedOperationException("remove not supported on RingIterator");
}
}
}